/*
* 最小生成树算法解析
* Prim 朴素 O(n^2) 邻接矩阵 枚举点 不可堆优化 

* Krusal O(mlogm) 存边 维护连通性使用并查集 已是最小生成树，加任意一条边就存在环，此时需要判决

* 普通的最小生成树：所有边权之和最小
* 本题的最小生成树：最大边权最小

* 做法:1.将所有边从小到大排序
       2.从小到大依次枚举 a, b, w
          case 1: a,b已连通，直接pass
          case 2：a,b不连通，选出当前边
*/
#pragma GCC optimize("O1,O2,O3,Ofast")
#pragma GCC optimize("no-stack-protector,unroll-loops,fast-math,inline")
#pragma GCC target("avx,avx2,fma")
#pragma GCC target("sse,sse2,sse3,sse4,sse4.1,sse4.2,ssse3")

#include<iostream>
#include<cstring>
#include<algorithm>

// #define ONLINE_GUDGE
using LL = long long;
using namespace std;
const int N = 310, M = 8010, INF = 0x3f3f3f3f;

int n, m;

// Krusal
struct Edge{    //用结构体存储每条边
    int f,t,w;
    bool operator<(const Edge &W)const{
        return w<W.w;
    }
}edges[M];
int fa[N];

int from(int x){    //并查集找根节点
    if(fa[x]!=x)
        fa[x]=from(fa[x]);
    return fa[x];
}
int cnt, ans, maxw;

int main()
{
    #ifdef ONLINE_JUDGE

    #else
        freopen("./in.txt", "r", stdin);
    #endif

    ios::sync_with_stdio(0); cin.tie(0);

    cin >> n >> m;

    for(int i = 0; i < m; i++){
        int a, b, w; cin >> a >> b >> w;
        edges[i] = Edge{a, b, w};  //加入当前的边
        }
    sort(edges, edges + m);  //对边进行排序

    for(int i = 1; i <= n; i++) fa[i] = i;    //并查集初始化

    for(int i = 0; i < m; i++){
        int f = from(edges[i].f), t = from(edges[i].t), w = edges[i].w;
        if(f != t){   //当前两点不连通
            fa[f] = t;    //让两点变连通
            maxw = w;
            // printf("%d - %d\n", i, w);
        }
    }
    printf("%d %d\n", n - 1, maxw); // 最小生成树边数 == 点数-1
    return 0;
}